前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# import export使用

30 秒速记

  • ESM(import / export)是静态的:编译期就确定依赖,能做 tree shaking;CJS(require)是运行时执行到才加载
  • ESM 导出是活绑定,导出方改了值导入方能看到;CJS 拿到的是 module.exports 那一刻的值
  • Node 怎么判断文件是哪种:.mjs 一定是 ESM,.cjs 一定是 CJS,.js 看最近的 package.json 里 "type"
  • 版本线:Node 8.5 / 9 要 --experimental-modules,12.17 / 13.2 起不用 flag,14 以后稳定
  • ESM 里没有 require、__dirname,要用 import.meta.url 或 Node 20.11+ 的 import.meta.dirname

import / export 是 JS 官方的模块标准 ESM,require 是 Node 早年自己搞的 CommonJS,两者最大的区别是一个静态、一个动态。 ESM 在代码运行前就把依赖关系分析好了,所以 import 只能写在顶层,打包工具也能据此删掉没用到的代码;CJS 是执行到 require 那行才去加载,可以写在 if 里。Node 对 ESM 的支持走了好几年:Node 9 时代要加实验 flag、文件必须叫 .mjs;现在只要 package.json 写 "type": "module",.js 文件就能直接用 import。

理解「import export使用」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

Node 早期只有 CommonJS,ES2015 定下了 import / export 标准之后,Node 花了好几个大版本才把两套体系接到一起。按时间看大致是:

  • Node 8.5 / 9:加 --experimental-modules 才能用,ESM 文件必须是 .mjs,import 一个 CJS 模块只能拿到默认导出
  • Node 12:引入 package.json 的 "type": "module",.js 也能当 ESM
  • Node 12.17 / 13.2:去掉 flag,14 起正式稳定
  • Node 20.11+:ESM 里有了 import.meta.dirname / import.meta.filename
  • Node 22.12 / 20.19:CJS 里可以直接 require 同步的 ESM 模块

两种写法放一起对比最直观:

// counter.mjs(ESM)
export let count = 0
export function inc() { count++ }

// main.mjs
import { count, inc } from './counter.mjs'
inc()
console.log(count) // 1,活绑定,跟着原变量走
@前端进阶之旅: 代码已经复制到剪贴板
// counter.cjs(CommonJS)
let count = 0
module.exports = { count, inc() { count++ } }

// main.cjs
const { count, inc } = require('./counter.cjs')
inc()
console.log(count) // 0,解构时拷贝的是当时的值
@前端进阶之旅: 代码已经复制到剪贴板

在 ESM 里,__dirname、__filename、require 都没有了:

import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// Node 20.11+ 可以直接用 import.meta.dirname
@前端进阶之旅: 代码已经复制到剪贴板

新项目我建议直接 "type": "module",老项目迁移时注意第三方包是不是纯 ESM、有没有依赖 __dirname 和 require.cache 的代码,这几处是最常卡住的地方。

面试官追问

追问 1a.js 里 export let count = 0 再导出一个 inc(),b.js 调 inc() 后读 count 是几?
参考回答

是 1,ESM 的导入是指向原变量的活绑定。换成 CJS 的 module.exports = { count, inc },b.js 读到的还是 0,因为导出的是当时值的拷贝。

追问 2项目里 .js 文件写了 import,一运行报 Cannot use import statement outside a module,怎么办?
参考回答

Node 把这个 .js 当成 CJS 了。在 package.json 里加 "type": "module",或者把文件改名成 .mjs。加了 type 之后,项目里还用 require 的文件要改成 .cjs。

追问 3在 ESM 文件里能直接 require 一个包吗?
参考回答

不能,ESM 作用域里没有 require。要么直接 import,CJS 包的 module.exports 会成为默认导出;实在要用就 import { createRequire } from 'node:module' 造一个出来。

追问 4反过来,CJS 文件里能 require 一个纯 ESM 的包吗?
参考回答

老版本不行,会报 ERR_REQUIRE_ESM,只能用 await import()。Node 22.12 和 20.19 以后默认支持 require(esm),但前提是那个模块里没有顶层 await。

追问 5a 导入 b,b 又导入 a,运行时 b 里拿到的 a 的导出是什么?
参考回答

ESM 里如果 b 在 a 执行完之前就访问 a 的 let / const 导出,会报 ReferenceError(暂时性死区);CJS 里拿到的是 a 还没执行完的半成品 exports,字段是 undefined。两种都是信号:该把公共部分抽到第三个模块里了。

# 前言

30 秒速记

  • Node 9 能在实验 flag 下直接跑 import / export,不用再靠 Babel 转
  • 当时是 Stability: 1 - Experimental,API 随时可能变,不适合直接上生产
  • 这是历史阶段:现在 ESM 早已稳定,flag 和 .mjs 强制要求都不再需要
  • 看旧教程要先对版本:Node 9 的写法放到 Node 18+ 上很多已经不适用

Node 9 的意义在于第一次能不经过 Babel,在 flag 模式下直接用 import / export 写服务端代码。 不过当时它还是实验特性,启动参数、文件后缀、loader 的写法后来都改过好几轮。现在回头看这段,我会把它当成了解 Node 模块演进的历史,而不是照抄的配置:Node 14 以后 ESM 已经稳定,新项目在 package.json 里写一句 "type": "module" 就能用,不用任何 flag。

Node 9最激动人心的是提供了在flag模式下使用ECMAScript Modules,虽然现在还是Stability: 1 - Experimental阶段,但是可以让Noder抛掉babel等工具的束缚,直接在Node环境下愉快地去玩耍import/export

如果觉得文字太多,看不下去,可以直接去玩玩demo,地址是 https://github.com/chenshenhai/node-modules-demo

面试官追问

追问 1有人照着这篇在 Node 20 上加 --experimental-modules 启动,有必要吗?
参考回答

没必要。这个 flag 在 Node 12.17 / 13.2 以后就不需要了,ESM 默认开启。现在只要文件是 .mjs 或者 package.json 里有 "type": "module" 就行。

追问 2当年为什么大家都用 Babel 来写 import?
参考回答

Node 原生不支持,Babel 把 import 转成 require 再交给 Node 跑。代价是多一层构建、调试要靠 sourcemap,而且转出来的其实还是 CJS 语义,活绑定这些特性并不完全一样。

追问 3实验特性在什么情况下能用到生产?
参考回答

一般不建议,Stability: 1 的 API 不受语义化版本保护,小版本升级都可能改行为。真要用就锁死 Node 版本,并且准备好回退到 Babel 或 require 的方案。

追问 4浏览器里的 import 和 Node 里的 import 行为完全一样吗?
参考回答

语法一样,解析规则不一样。浏览器只认完整 URL 或相对路径,裸写 'react' 要靠 importmap;Node 能按 node_modules 找包,还有 package.json 的 exports 字段控制入口。

#

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
      • 前言
      • Node 9下import/export使用简单须知
      • 使用简述
        • 与require()区别
      • Loader Hooks模式使用
      • Koa2 直接使用import/export
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

import export使用|Nodejs专题系列

# import export使用

30 秒速记

  • ESM(import / export)是静态的:编译期就确定依赖,能做 tree shaking;CJS(require)是运行时执行到才加载
  • ESM 导出是活绑定,导出方改了值导入方能看到;CJS 拿到的是 module.exports 那一刻的值
  • Node 怎么判断文件是哪种:.mjs 一定是 ESM,.cjs 一定是 CJS,.js 看最近的 package.json 里 "type"
  • 版本线:Node 8.5 / 9 要 --experimental-modules,12.17 / 13.2 起不用 flag,14 以后稳定
  • ESM 里没有 require、__dirname,要用 import.meta.url 或 Node 20.11+ 的 import.meta.dirname

import / export 是 JS 官方的模块标准 ESM,require 是 Node 早年自己搞的 CommonJS,两者最大的区别是一个静态、一个动态。 ESM 在代码运行前就把依赖关系分析好了,所以 import 只能写在顶层,打包工具也能据此删掉没用到的代码;CJS 是执行到 require 那行才去加载,可以写在 if 里。Node 对 ESM 的支持走了好几年:Node 9 时代要加实验 flag、文件必须叫 .mjs;现在只要 package.json 写 "type": "module",.js 文件就能直接用 import。

理解「import export使用」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

Node 早期只有 CommonJS,ES2015 定下了 import / export 标准之后,Node 花了好几个大版本才把两套体系接到一起。按时间看大致是:

  • Node 8.5 / 9:加 --experimental-modules 才能用,ESM 文件必须是 .mjs,import 一个 CJS 模块只能拿到默认导出
  • Node 12:引入 package.json 的 "type": "module",.js 也能当 ESM
  • Node 12.17 / 13.2:去掉 flag,14 起正式稳定
  • Node 20.11+:ESM 里有了 import.meta.dirname / import.meta.filename
  • Node 22.12 / 20.19:CJS 里可以直接 require 同步的 ESM 模块

两种写法放一起对比最直观:

// counter.mjs(ESM)
export let count = 0
export function inc() { count++ }

// main.mjs
import { count, inc } from './counter.mjs'
inc()
console.log(count) // 1,活绑定,跟着原变量走
@前端进阶之旅: 代码已经复制到剪贴板
// counter.cjs(CommonJS)
let count = 0
module.exports = { count, inc() { count++ } }

// main.cjs
const { count, inc } = require('./counter.cjs')
inc()
console.log(count) // 0,解构时拷贝的是当时的值
@前端进阶之旅: 代码已经复制到剪贴板

在 ESM 里,__dirname、__filename、require 都没有了:

import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// Node 20.11+ 可以直接用 import.meta.dirname
@前端进阶之旅: 代码已经复制到剪贴板

新项目我建议直接 "type": "module",老项目迁移时注意第三方包是不是纯 ESM、有没有依赖 __dirname 和 require.cache 的代码,这几处是最常卡住的地方。

面试官追问

追问 1a.js 里 export let count = 0 再导出一个 inc(),b.js 调 inc() 后读 count 是几?
参考回答

是 1,ESM 的导入是指向原变量的活绑定。换成 CJS 的 module.exports = { count, inc },b.js 读到的还是 0,因为导出的是当时值的拷贝。

追问 2项目里 .js 文件写了 import,一运行报 Cannot use import statement outside a module,怎么办?
参考回答

Node 把这个 .js 当成 CJS 了。在 package.json 里加 "type": "module",或者把文件改名成 .mjs。加了 type 之后,项目里还用 require 的文件要改成 .cjs。

追问 3在 ESM 文件里能直接 require 一个包吗?
参考回答

不能,ESM 作用域里没有 require。要么直接 import,CJS 包的 module.exports 会成为默认导出;实在要用就 import { createRequire } from 'node:module' 造一个出来。

追问 4反过来,CJS 文件里能 require 一个纯 ESM 的包吗?
参考回答

老版本不行,会报 ERR_REQUIRE_ESM,只能用 await import()。Node 22.12 和 20.19 以后默认支持 require(esm),但前提是那个模块里没有顶层 await。

追问 5a 导入 b,b 又导入 a,运行时 b 里拿到的 a 的导出是什么?
参考回答

ESM 里如果 b 在 a 执行完之前就访问 a 的 let / const 导出,会报 ReferenceError(暂时性死区);CJS 里拿到的是 a 还没执行完的半成品 exports,字段是 undefined。两种都是信号:该把公共部分抽到第三个模块里了。

# 前言

30 秒速记

  • Node 9 能在实验 flag 下直接跑 import / export,不用再靠 Babel 转
  • 当时是 Stability: 1 - Experimental,API 随时可能变,不适合直接上生产
  • 这是历史阶段:现在 ESM 早已稳定,flag 和 .mjs 强制要求都不再需要
  • 看旧教程要先对版本:Node 9 的写法放到 Node 18+ 上很多已经不适用

Node 9 的意义在于第一次能不经过 Babel,在 flag 模式下直接用 import / export 写服务端代码。 不过当时它还是实验特性,启动参数、文件后缀、loader 的写法后来都改过好几轮。现在回头看这段,我会把它当成了解 Node 模块演进的历史,而不是照抄的配置:Node 14 以后 ESM 已经稳定,新项目在 package.json 里写一句 "type": "module" 就能用,不用任何 flag。

Node 9最激动人心的是提供了在flag模式下使用ECMAScript Modules,虽然现在还是Stability: 1 - Experimental阶段,但是可以让Noder抛掉babel等工具的束缚,直接在Node环境下愉快地去玩耍import/export

如果觉得文字太多,看不下去,可以直接去玩玩demo,地址是 https://github.com/chenshenhai/node-modules-demo

面试官追问

追问 1有人照着这篇在 Node 20 上加 --experimental-modules 启动,有必要吗?
参考回答

没必要。这个 flag 在 Node 12.17 / 13.2 以后就不需要了,ESM 默认开启。现在只要文件是 .mjs 或者 package.json 里有 "type": "module" 就行。

追问 2当年为什么大家都用 Babel 来写 import?
参考回答

Node 原生不支持,Babel 把 import 转成 require 再交给 Node 跑。代价是多一层构建、调试要靠 sourcemap,而且转出来的其实还是 CJS 语义,活绑定这些特性并不完全一样。

追问 3实验特性在什么情况下能用到生产?
参考回答

一般不建议,Stability: 1 的 API 不受语义化版本保护,小版本升级都可能改行为。真要用就锁死 Node 版本,并且准备好回退到 Babel 或 require 的方案。

追问 4浏览器里的 import 和 Node 里的 import 行为完全一样吗?
参考回答

语法一样,解析规则不一样。浏览器只认完整 URL 或相对路径,裸写 'react' 要靠 importmap;Node 能按 node_modules 找包,还有 package.json 的 exports 字段控制入口。

# Node 9下import/export使用简单须知

30 秒速记

  • Node 9 用 ESM 的四个条件:版本 ≥ 9、文件用 .mjs、启动加 --experimental-modules、语法严格按 ESM 写
  • 不加 loader 时 .js 一律当 CommonJS,在 .js 里写 import 会直接报语法错
  • ESM 和 require 是两套缓存,删 require.cache 管不到 import 进来的模块
  • 这些是 Node 9 的限制;现在 .js 可以通过 "type": "module" 当 ESM,flag 也不用加了

在 Node 9 里用 import / export,要同时满足版本、后缀、启动参数和语法四个条件,少一个都跑不起来。 最常踩的是只改了语法没改后缀,.js 文件在当时默认按 CommonJS 解析,一遇到 import 就报错;其次是忘了加 --experimental-modules。还有一个容易忽略的点:两套模块各有各的缓存,以前靠删 require.cache 做热更新的代码,对 import 进来的模块完全不起作用。

  • Node 环境必须在 9.0以上
  • 不加loader时候,使用import/export的文件后缀名必须为*.mjs(下面会讲利用Loader Hooks兼容*.js后缀文件)
  • 启动必须加上flag --experimental-modules
  • 文件的import和export必须严格按照ECMAScript Modules语法
  • ECMAScript Modules和require()的cache机制不一样

← 12.9 session登录态判断处理2.1 原生koa2实现路由 →

fe